/**
 * author      : dormi330
 * date        : 2018/6/27
 * project     : mybatis
 * description : 支付一个已经存在的订单
 */

package com.deep.trade.domain.order;

import com.deep.trade.adaptor.required.DistributeLockService;
import com.deep.trade.adaptor.required.IdService;
import com.deep.trade.adaptor.required.payment.PayService;
import com.deep.trade.common.TradeException;
import com.deep.trade.infrastructure.utils.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

@Service
public class CommandPayOrder {

    private static final int ONLY_ONE_SHOULD_BE_UPDATED = 1;

    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Autowired
    private PayService payService;

    @Autowired
    private IdService idService;

    @Autowired
    private OrderQueryRepository orderQueryRepository;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private DistributeLockService dLockService;

    @Transactional
    public void payOrderSummary(OrderSummary orderSummary, String payChannel, String payType) throws Exception {

        logger.info("{}, orderSummary={}, payChannel={}, payType={}", CommonUtils.currentClassAndMethod(), orderSummary, payChannel, payType);

        if (null == orderSummary) {
            throw new TradeException("argument cannot be null");
        }

        String tenantCode = orderSummary.getTenantCode();
        String orderSummaryId = orderSummary.getId();
        // 检测订单是否可以支付
        List<TradeOrder> tradeOrderList = orderQueryRepository.tradeOderOfOrderSummary(tenantCode, orderSummaryId);
        if (!canOrderSummaryBePaid(orderSummary, tradeOrderList)) {
            throw new TradeException("the order cannot be paid, orderSummaryId=" + orderSummary.getId());
        }

        String lockName = "orderSummary-" + tenantCode + "-" + orderSummaryId;

        try {
            // 分布式锁, 防止 doPay多次
            // 考虑极端情况下, 使用多个渠道对这个订单进行支付
            dLockService.getLock(lockName);
            doPay(orderSummary.getPayMoney(), orderSummaryId, payChannel);
        } catch (Exception ex) {
            // 如果支付失败, 可以继续支付, 状态不进行变更
            ex.printStackTrace();
            logger.error(ex.toString());
            throw new TradeException("order fail due to pay service failed");
        } finally {
            dLockService.releaseLock(lockName);
        }

        // TODO 整个情况应该是回调通知
        logger.warn("TODO 整个情况应该是回调通知, 而不是直接调用");
        afterOrderSummaryPaid(payChannel, payType, orderSummary, tradeOrderList);
    }

    @Transactional
    public void payTradeOrder(TradeOrder tradeOrder, String payChannel, String payType) throws Exception {
        if (null == tradeOrder) {
            throw new TradeException("argument cannot be null");
        }

        String tenantCode = tradeOrder.getTenantCode();
        String tradeOrderId = tradeOrder.getId();
        // 检测订单是否可以支付
        if (!TradeOrder.STATUS_TO_BE_PAID.equals(tradeOrder.getStatus())) {
            throw new TradeException("the order cannot be paid, status=" + tradeOrder.getStatus());
        }

        String lockName = "tradeOrder-" + tenantCode + "-" + tradeOrderId;
        try {
            // 分布式锁, 防止 doPay多次
            dLockService.getLock(lockName);
            doPay(tradeOrder.getPayMoney(), tradeOrder.getId(), payChannel);
        } catch (Exception ex) {
            // 如果支付失败, 可以继续支付, 状态不进行变更
            ex.printStackTrace();
            logger.error(ex.toString());
            throw new TradeException("pay order fail due to pay service failed");
        } finally {
            dLockService.releaseLock(lockName);
        }

        // TODO 整个情况应该是回调通知
        logger.warn("TODO 整个情况应该是回调通知, 而不是直接调用");
        afterTradeOrderPaid(payChannel, payType, tradeOrder);
    }


    // 检测订单 是否可以支付
    // 所有的TradeOrder都必须是 待支付 状态
    private boolean canOrderSummaryBePaid(OrderSummary orderSummary, List<TradeOrder> tradeOrderList) {
        boolean canPay = null == orderSummary.getPaidAt()
                && orderSummary.getPayMoney().compareTo(BigDecimal.ZERO) == 1;

        for (TradeOrder tradeOrder : tradeOrderList) {
            if (!TradeOrder.STATUS_TO_BE_PAID.equals(tradeOrder.getStatus())) {
                canPay = false;
                break;
            }
        }

        return canPay;
    }

    /**
     * 正常情况下, mq消息订阅, 触发这个方法的调用, 所以是 public
     * 订单【支付成功】后的动作, 包括 更新 tradeOrder + 插入一条 OrderPayInfo记录
     */
    @Transactional
    public void afterTradeOrderPaid(String payChannel, String payType, TradeOrder tradeOrder) throws Exception {

        tradeOrder.setStatus(TradeOrder.STATUS_PAID);
        if (ONLY_ONE_SHOULD_BE_UPDATED != orderRepository.updateTradeOrderStatus(tradeOrder)) {
            throw new TradeException("fail: update trade order after paid ");
        }

        // TODO 更新 OrderPayment
        OrderPayment orderPayment = new OrderPayment();
        orderPayment.setTenantCode(tradeOrder.getTenantCode());
        //  BeanUtils.copyProperties(tradeOrder, orderPayment);
        orderPayment.setId(idService.uuid());
        orderPayment.setCreatedBy(tradeOrder.getMemberId());
        orderPayment.setUpdatedBy(tradeOrder.getMemberId());
        //
        orderPayment.setTradeOrderId(tradeOrder.getId());
        orderPayment.setPayType(payType);
        orderPayment.setPayChannel(payChannel);
        orderPayment.setPayStatus(TradeOrder.STATUS_PAID);
        orderPayment.setPaidAt(new Date());
        orderPayment.setPayMoney(tradeOrder.getPayMoney());

        orderRepository.addOrderPayInfo(orderPayment);
    }

    // 进行支付, 实际支付待定 TODO
    private void doPay(BigDecimal money, String orderNo, String payChannel) {
        payService.pay(money, orderNo, payChannel);
    }

    /**
     * 正常情况下, mq消息订阅, 触发这个方法的调用, 所以是 public
     * 订单【支付成功】后的动作
     */
    @Transactional
    public void afterOrderSummaryPaid(String payChannel, String payType, OrderSummary orderSummary, List<TradeOrder> tradeOrderList) throws Exception {

        // 更新 orderSummary
        orderSummary.setPaidAt(new Date());
        orderSummary.setPayChannel(payChannel);
        orderSummary.setUpdatedBy("_SYSTEM_");  // 系统更新
        if (ONLY_ONE_SHOULD_BE_UPDATED != orderRepository.updateOrderSummaryToPaid(orderSummary)) {
            throw new TradeException("fail: update order summary after paid ");
        }

        // 更新 tradeOrder
        for (TradeOrder tradeOrder : tradeOrderList) {
            afterTradeOrderPaid(payChannel, payType, tradeOrder);
        }
    }


}
